/*
*
==============================================================================
*  Name        : 
*  Part of     : Omen Game Engine - OpenGL Plugin
*  Description : 
*  Version     : 1.0
*
*  Copyright (c) 2013 Lauri Kortevaara.
* 
==============================================================================
*/

#include "OpenGLWindow.h"
#include "SystemInfo.h"

#include <Windows.h>
#include <Strsafe.h>

//=============================================================================
/**
**  Params
*/
HINSTANCE hInstance;

bool OpenGLWindow::keys[256];
std::map<HWND,OpenGLWindow*> OpenGLWindow::g_mapHwndGLWindow;

//=============================================================================
/**
** <summary> OpenGLWindow constructor </summary>
**
*/
OpenGLWindow::OpenGLWindow()
    : m_hwnd(NULL),
      m_glRenderingContext(NULL)
{
}

//=============================================================================
/**
** <summary> OpenGLWindow copy-constructor </summary>
**
*/
OpenGLWindow::OpenGLWindow( const OpenGLWindow& other )
{
    m_hwnd = other.m_hwnd;
    m_glRenderingContext = other.m_glRenderingContext;
    m_windowHeight = other.m_windowHeight;
    m_windowWidth = other.m_windowWidth;
}


static void ShowLastError( LPTSTR lpszFunction )
{
    // Retrieve the system error message for the last-error code
    LPVOID lpMsgBuf;
    LPVOID lpDisplayBuf;
    DWORD dw = GetLastError(); 

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0, NULL );

    // Display the error message and exit the process

    lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, 
        (lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR)); 
    StringCchPrintf((LPTSTR)lpDisplayBuf, 
        LocalSize(lpDisplayBuf) / sizeof(TCHAR),
        TEXT("%s failed with error %d: %s"), 
        lpszFunction, dw, lpMsgBuf); 
    MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK); 

    LocalFree(lpMsgBuf);
    LocalFree(lpDisplayBuf);
}
//=============================================================================
/**
** <summary> This Create a new OpenGL Window </summary>
**
*/
void OpenGLWindow::createWindow( const int& windowWidth, const int& windowHeight, const bool& bFullScreen )
{
    registerWindowClass();

    SystemInfo::getDisplayCardInfo();
    SystemInfo::getSupportedDisplayModes();

    DWORD   dwExStyle;                      // Window Extended Style
    DWORD   dwStyle;                        // Window Style
    
    if( bFullScreen )
    {
        dwExStyle=WS_EX_APPWINDOW;                  // Window Extended Style
        dwStyle=WS_POPUP;                       // Windows Style
        ShowCursor(FALSE);                      // Hide Mouse Pointer
    }
    else
    {
        dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;     // Window Extended Style
        dwStyle   = WS_OVERLAPPEDWINDOW;                    // Windows Style
    }

    int WindowWidth = windowWidth;
    int WindowHeight = windowHeight;

    hInstance = GetModuleHandleA(NULL);
    HWND hWnd = CreateWindowEx(  dwExStyle,              // Extended Style For The Window
                                L"OpenGL",               // Class Name
                                L"My OpenGL Window",                  // Window Title
                                WS_CLIPSIBLINGS |           // Required Window Style
                                WS_CLIPCHILDREN |           // Required Window Style
                                dwStyle,                // Selected Window Style
                                0, 0,                   // Window Position
                                WindowWidth,   // Calculate Adjusted Window Width
                                WindowHeight,   // Calculate Adjusted Window Height
                                NULL,                   // No Parent Window
                                NULL,                   // No Menu
                                hInstance,              // Instance
                                NULL);                 // Don't Pass Anything To WM_CREATE
    if (!IsWindow(hWnd)) 
    {
        ShowLastError(L"createWindow");
        throw std::exception( "Could not create the opengl window!" );
    }

    m_hwnd = hWnd;
    // Map window(hWnd) to the this instance(this)
    g_mapHwndGLWindow[hWnd] = this;

    choosePixelFormat();
    createGLContext();
    ShowWindow(m_hwnd, SW_SHOW);
}

//=============================================================================
/**
** <summary> Function will register a new window class for the OpenGL Window purposes </summary>
**
*/
void OpenGLWindow::registerWindowClass()
{
    WNDCLASSEX ex;

    // Grab a new instance for our purpose
    hInstance = GetModuleHandleA(NULL);

    ex.cbSize = sizeof(WNDCLASSEX);
    ex.style = CS_HREDRAW|CS_VREDRAW|CS_OWNDC;
    ex.lpfnWndProc = OpenGLWindow::WindowProcedure;
    ex.cbClsExtra = 0;
    ex.cbWndExtra = 0;
    ex.hInstance = hInstance;
    ex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    ex.hCursor = LoadCursor(NULL, IDC_ARROW);
    ex.hbrBackground = NULL;
    ex.lpszMenuName = NULL;
    ex.lpszClassName = L"OpenGL";
    ex.hIconSm = NULL;

    ATOM result = RegisterClassEx(&ex);

    if( result == 0 )
        throw std::exception("Could not create the window class!");
}

//=============================================================================
/**
** <summary> Function will select a proper pixel format for desired parameters </summary>
**
*/
void OpenGLWindow::choosePixelFormat()
{
    HDC hDC = GetDC(m_hwnd);
    if( !hDC )
        throw std::exception( "Couldn't get the device context for OpenGL Window!" );

    char bits = 32;

    static  PIXELFORMATDESCRIPTOR pfd=      // pfd Tells Windows How We Want Things To Be
    {
        sizeof(PIXELFORMATDESCRIPTOR),      // Size Of This Pixel Format Descriptor
        1,                                  // Version Number
        PFD_DRAW_TO_WINDOW |                // Format Must Support Window
        PFD_SUPPORT_OPENGL |                // Format Must Support OpenGL
        PFD_DOUBLEBUFFER,                   // Must Support Double Buffering
        PFD_TYPE_RGBA,                      // Request An RGBA Format
        bits,                               // Select Our Color Depth
        0, 0, 0, 0, 0, 0,                   // Color Bits Ignored
        0,                                  // No Alpha Buffer
        0,                                  // Shift Bit Ignored
        0,                                  // No Accumulation Buffer
        0, 0, 0, 0,                         // Accumulation Bits Ignored
        16,                                 // 16Bit Z-Buffer (Depth Buffer)
        0,                                  // No Stencil Buffer
        0,                                  // No Auxiliary Buffer
        PFD_MAIN_PLANE,                     // Main Drawing Layer
        0,                                  // Reserved
        0, 0, 0                             // Layer Masks Ignored
    };

    int pixelFormat = ChoosePixelFormat( hDC, &pfd );

    if( !pixelFormat )
        throw std::exception( "Couldn't find a suitable pixel format for OpenGL window!" );

    BOOL bOk = SetPixelFormat( hDC, pixelFormat, &pfd );
    if( !bOk )
        throw std::exception( "Couldn't select the suitable pixel format for OpenGL window!" );
}

//=============================================================================
/**
** <summary> Function will create the rendering context for the OpenGL Window</summary>
**
*/
void OpenGLWindow::createGLContext()
{
    HDC hDC = GetDC(m_hwnd);
    
    if( !hDC )
        throw std::exception( "Couldn't get the device context for OpenGL Window!" );

    HGLRC hRC = wglCreateContext(hDC);

    if( !hRC )
        throw std::exception( "Couldn't create the rendering context for OpenGL Window!" );

    BOOL bOk = wglMakeCurrent(hDC,hRC);

    if( !bOk )
        throw std::exception( "Couldn't get the device context for OpenGL Window!" );

    

    m_glRenderingContext = hRC;
}

//=============================================================================
/**
** <summary> This is the window message processor</summary>
**
*/
LRESULT CALLBACK OpenGLWindow::WindowProcedure(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    if( g_mapHwndGLWindow.empty() )
        return DefWindowProc(hWnd,message,wParam,lParam);
    OpenGLWindow* pWnd = g_mapHwndGLWindow.at(hWnd);
    if( pWnd )
    {
        switch( message )
        {
            case WM_PAINT:
            {
                wglMakeCurrent(GetDC(pWnd->m_hwnd),pWnd->m_glRenderingContext);
                pWnd->renderScene();
                pWnd->swapBuffers();
                //wglMakeCurrent(NULL,NULL);
                return 0;
            }
            case WM_ACTIVATE:                            // Watch For Window Activate Message
            {
                /*if (!HIWORD(wParam))                    // Check Minimization State
                {
                    OpenGLGraphicsDriver::instance()->setActive(true);
                }
                else
                {
                    OpenGLGraphicsDriver::instance()->setActive(false);
                }*/

                return 0;                                // Return To The Message Loop
            }

            case WM_SYSCOMMAND:
            {
                switch (wParam)
                {
                    case SC_SCREENSAVE:
                    case SC_MONITORPOWER:
                        return 0;
                }
                break;
            }

            case WM_CLOSE:                                // Did We Receive A Close Message?
            {
                PostQuitMessage(0);                        // Send A Quit Message
                return 0;                                // Jump Back
            }
            /*case WM_KEYDOWN:                            // Is A Key Being Held Down?
                {
                keys[wParam] = TRUE;                    // If So, Mark It As TRUE
            
                // If ctrl and end pressed, turn auto scroll on
                //if( keys[VK_CONTROL] & keys[VK_END] )
                //    g_bAutoScroll = true;
                return 0;                                // Jump Back
            }

            case WM_KEYUP:                                // Has A Key Been Released?
            {
                keys[wParam] = FALSE;                    // If So, Mark It As FALSE

                /*char asciiChar = wParam;
                if( asciiChar > 32 && asciiChar < 127  || wParam == VK_SPACE  || wParam == VK_RETURN )
                {
                    if (GetKeyState(VK_SHIFT) & 0x8000)
                        debugLog += asciiChar;
                    else
                        debugLog += tolower(asciiChar);
                }

                if( wParam == VK_BACK && !debugLog.empty() )
                    debugLog.pop_back();*
                return 0;                                // Jump Back
            }*/

            case WM_MOUSEWHEEL:
            {
                /*gYSpeed -= GET_WHEEL_DELTA_WPARAM(wParam);
            
                // If mouse wheel rolled, turn auto scroll off
                g_bAutoScroll = false;*/
                return 0;
            }


            case WM_SIZE:                                // Resize The OpenGL Window
            {
                pWnd->m_windowWidth = LOWORD(lParam);
                pWnd->m_windowHeight = HIWORD(lParam);
                //ReSizeGLScene(LOWORD(lParam),HIWORD(lParam));  // LoWord=Width, HiWord=Height
                return 0;                                // Jump Back
            }
        }
    }
    // Pass All Unhandled Messages To DefWindowProc
    return DefWindowProc(hWnd,message,wParam,lParam);
    return 0;
}

//=============================================================================
/**
**  Swap the opengl back buffer to the windows front buffer, i.e. visible
*/
void OpenGLWindow::swapBuffers()
{
    HDC hDC = GetDC(m_hwnd);
    SwapBuffers(hDC);
}

void OpenGLWindow::renderScene()
{
}


int OpenGLWindow::width() const
{
    return m_windowWidth;
}

int OpenGLWindow::height() const
{
    return m_windowHeight;
}